package vista.objetos;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import controlador.ContenedorDeErrores;
import controlador.ControladorDeTiposDeObjetos;
import controlador.IConversorLogicoAGraficable;
import controlador.NoExisteEntidadGraficable;

import logica.Atributo;
import logica.Diagrama;
import logica.DiagramaSecundario;
import logica.Entidad;
import logica.EntidadRepetidaException;
import logica.IdExternoInexistenteException;
import logica.Jerarquia;
import logica.Proyecto;
import logica.Relacion;
import logica.RelacionException;
import logica.SinNombreException;
import logica.persistencia.ProyectoGraficablePersistente;

public class ProyectoGraficable {
	
	private Proyecto proyecto;
	private DiagramaGraficable diagramaPpal; 
	private DiagramaGraficable diagramaActual;
	
	/*
	 * Carga un proyecto ya existente
	 */
	public ProyectoGraficable(Proyecto proyecto, DiagramaGraficable diagramaPpal) 
	{
		this.setProyecto(proyecto);
		this.setDiagramaPpal(diagramaPpal);
		this.setDiagramaActual(diagramaPpal);
	}

	/*
	 * crea un proyecto vacío
	 */
	public ProyectoGraficable(Proyecto proyecto)
	{
		this.setProyecto(proyecto);
		this.setDiagramaPpal(new DiagramaGraficable(proyecto.getDiagramaPpal()));
		this.setDiagramaActual(diagramaPpal);
	}
	
	public void crearDiagramaHijoVacio(String nombre) throws SinNombreException
	{
		DiagramaSecundario diagLog = this.getProyecto().crearDiagramaHijo(nombre);
		DiagramaGraficable diagGraf = new DiagramaGraficable(diagLog);

		this.agregarDiagramarHijo(diagGraf, diagLog);
	}
	
	public void crearDiagramaHijo(String nombre, List<GraficableConEtiqueta> objetosACopiar) throws SinNombreException, EntidadRepetidaException, IdExternoInexistenteException
	{
		DiagramaSecundario diagLog = this.getProyecto().crearDiagramaHijo(nombre);
		DiagramaGraficable diagGraf = new DiagramaGraficable(diagLog, objetosACopiar);

		this.agregarDiagramarHijo(diagGraf, diagLog);

	}
	
	private void agregarDiagramarHijo(DiagramaGraficable diagGraf, Diagrama diagLog)
	{
		this.agregarDiagramaHijoGraficable(diagGraf);
		this.getProyecto().setDiagramaActual(diagLog);
	}
	
	private void agregarDiagramaHijoGraficable(DiagramaGraficable diagramaHijo)
	{
		this.diagramaActual.agregarDiagramaHijo(diagramaHijo);
		
		diagramaHijo.setSoyElDiagramaActual(true);
		diagramaHijo.setOrientacion(this.diagramaActual.getOrientacion());
		
		this.diagramaActual = diagramaHijo;
	}
		
	public Proyecto getProyecto() {
		return proyecto;
	}

	public void setProyecto(Proyecto proyecto) {
		this.proyecto = proyecto;
	}

	public DiagramaGraficable getDiagramaPpal() {
		return diagramaPpal;
	}

	public void setDiagramaPpal(DiagramaGraficable diagramaPpal) {
		this.diagramaPpal = diagramaPpal;
	}

	public DiagramaGraficable getDiagramaActual() {
		return diagramaActual;
	}

	public void setDiagramaActual(DiagramaGraficable diagramaActual) 
	{
		if (this.diagramaActual != null)
			this.diagramaActual.setSoyElDiagramaActual(false);
		
		this.diagramaActual = diagramaActual;
		this.diagramaActual.setSoyElDiagramaActual(true);
		this.getProyecto().setDiagramaActual(this.diagramaActual.getDiagrama());
	}
	
	public ProyectoGraficablePersistente getProyectoPersistente() {
		return new ProyectoGraficablePersistente(this);
	}
	
	/* 
	 * Entidad (no existente en el modelo) --> EntidadGraficable */
	public EntidadGraficable getEntidadGraficable(Entidad entidad) throws EntidadTipoNoSoportadoException, AtributoTipoNoSoportadoException, IdExternoInexistenteException, GraficableException
	{
		Iterator<FabricaEntidadGraficable> itTipoEntidad = this.getTipoDeEntidades().iterator();
		
		while (itTipoEntidad.hasNext())
		{
			FabricaEntidadGraficable fabTemp = itTipoEntidad.next();
			
			if (entidad.getTipo() == fabTemp.getTipoDeEntidad())
				return fabTemp.crearEntidadGraficable(entidad, this.getDiagramaActual(), ControladorDeTiposDeObjetos.getInstancia());
		}
		
		throw new EntidadTipoNoSoportadoException("El tipo <" + entidad.getTipo() + "> no es soportado por la aplicacion.");
	}
	
	/* 
	 * Relacion --> RelacionGraficable con referencia a las entidades graficables correspondientes al diagrama actual
	 */
	public RelacionGraficable getRelacionGraficable(Relacion relacion) throws RelacionTipoNoSoportadoException
	{
		Iterator<FabricaRelacionGraficable> itTipoEntidad = this.getTipoDeRelaciones().iterator();
		
		while (itTipoEntidad.hasNext())
		{
			FabricaRelacionGraficable fabTemp = itTipoEntidad.next();
			
			if (relacion.getTipo() == fabTemp.getTipoDeRelacion())
				return fabTemp.crearRelacionGraficable(relacion);
		}
		
		throw new RelacionTipoNoSoportadoException("El tipo <" + relacion.getTipo() + "> no es soportado por la aplicacion.");
	}
	
	/*
	 * Recibe una jerarquia graficable con las entidades lógicas ya seteadas y setea las respectivas
	 * entidades graficables
	 */
	public void agregarEntidadesGraficables(JerarquiaGraficable jerarquiaGraf) throws NoExisteEntidadGraficable
	{
		Jerarquia jerarquia = (Jerarquia)jerarquiaGraf.getObjetoDiagramaDecorado();
		
		jerarquiaGraf.setEntidadPadre(this.diagramaActual.getEntidadGraficableExistente(jerarquia.getEntidadPadre()));
		
		Iterator<Entidad> itEntidad = jerarquia.getEntidadesEspecializadas().iterator();
		List<EntidadGraficable> listaEntGraf = new LinkedList<EntidadGraficable>();
		
		while (itEntidad.hasNext())
			listaEntGraf.add(this.diagramaActual.getEntidadGraficableExistente(itEntidad.next()));

		jerarquiaGraf.setEntidadesEspecializadas(listaEntGraf);
	}
	
	/*
	 * Recibe un graficable que fue editado y se encarga de ver si puede aceptarse los nuevos cambios
	 * si es que hubo cambios en la entidades. Por ejemplo, que haya agregado una entidad graficable que 
	 * existía en un diagrama graficable, pero esta no existe en otro diagrama graficable donde se encuentra una
	 * copia de dicho graficable
	 * 
	 * 	entidades 	= las entidades lógicas de las cuales depende el graficable q son las misma de la cual depende
	 * 					el objeto lógico representado por ese graficable. 
	 * 	graficables = las distintas vistas del graficable. Es decir, todas las copias del mismo en algún otro dia-
	 * 					grama.
	 */
	public void verificarSiEntidadesEstan(List<Entidad> entidades, Graficable graficable) throws NoExisteEntidadGraficable
	{
		List<GraficableConEtiqueta> graficables;
		try {
			graficables = graficable.getObjetoDiagramaDecorado().getGraficables();
		} catch (NoEsObjetoDiagramableException e) { //no debería pasar
			e.printStackTrace();
			return;
		}
		
		for(Entidad entidad: entidades)
			this.verificarSiTieneUnaEntidad(entidad, graficables);
	}
	
	private void verificarSiTieneUnaEntidad(Entidad entidad, List<GraficableConEtiqueta> graficables) throws NoExisteEntidadGraficable
	{
		DiagramaGraficable diagrama = null;
	
		for(GraficableConEtiqueta graficable: graficables)
		{
			diagrama = graficable.getDiagrama();
			
//			System.out.println("ProyectoGraficable verificarSiTieneUnaEntidad()");
//			System.out.println("Diagrama: "  + diagrama);
//			System.out.println("Diagrama: "  + diagrama.getDiagrama().getNombre());
//			System.out.println("Entidad: " + entidad.getNombre());
			if(!diagrama.tieneUnGraficableDe(entidad))
				throw new NoExisteEntidadGraficable("No existe la entidad: "+ entidad.getNombre() + " en el diagrama: " + diagrama.getDiagrama().getNombre());
		}
	}
	
	/*agrega las entidades hijas a todas las copias de los graficables de dicha jerarquia*/
	public void agregarEntidadesGraficablesATodasLasJerarquiasCopias(JerarquiaGraficable jerarquiaGraf) throws NoExisteEntidadGraficable
	{
		Jerarquia jerarquia = (Jerarquia)jerarquiaGraf.getObjetoDiagramaDecorado();
		List<EntidadGraficable> listaEntGraf;
		JerarquiaGraficable jerarquiaGraficable;
		DiagramaGraficable diagrama;
		
		for(GraficableConEtiqueta graficable: jerarquia.getGraficables())
		{
			jerarquiaGraficable = (JerarquiaGraficable) graficable;
			listaEntGraf = new LinkedList<EntidadGraficable>();
			diagrama = jerarquiaGraficable.getDiagrama();
			
			for(Entidad entidadHija: jerarquia.getEntidadesEspecializadas())
				listaEntGraf.add(diagrama.getEntidadGraficableExistente(entidadHija));

			jerarquiaGraficable.setEntidadesEspecializadas(listaEntGraf);
		}
	}
	
	/*	 
	 * Recibe un relación graficable que fue editado y se encarga de ver si puede aceptarse los nuevos cambios
	 * en los atributos y en las entidades.
	 *  
	 * 	relacionGraficable	= la relación editada que disparó la acción. 
	 */
	public void verficarSiSePuedeEditar(RelacionGraficable relacion) throws AtributoTipoNoSoportadoException, GraficableException, NoExisteEntidadGraficable, RelacionException
	{
		Relacion relacionLogica = (Relacion)relacion.getObjetoDiagramaDecorado();
		
		//verifico que todas las entidades de la relación existan en todos los diagramas donde está copiada la relación
		this.verificarSiEntidadesEstan(relacionLogica.getReferenciaAEntidadesExternas(), relacion);

		//si lo de arriba es ok, entonces, verifico atributos y validez de relación fuerte-débil
		List<AtributoGraficable> atributos = this.getAtributoGraficables(relacionLogica.getAtributos());
		List<EntidadRolGraficable> entidadesRol = this.getDiagramaActual().getEntidadRolGraficables(relacionLogica.getEntidades());
		RelacionGraficable relacionGraficable;
			
		for(GraficableConEtiqueta graficable: relacionLogica.getGraficables())
		{
			relacionGraficable = (RelacionGraficable) graficable;
			
			relacionGraficable.verificarConsistenciaDeAtributos(atributos);
			relacionGraficable.verificarSiEsRelacionDebilFuerte(entidadesRol);
		}
	}
	
	/*	 
	 * Recibe una entidad graficable que fue editado y se encarga de ver si puede aceptarse los nuevos cambios
	 * en los atributos y en las entidades.
	 *  
	 * 	entidadGraficable	= la entidad editada que disparó la acción. 
	 */
	public void verficarSiSePuedeEditar(EntidadGraficable entidad) throws NoExisteEntidadGraficable, AtributoTipoNoSoportadoException, GraficableException, IdExternoInexistenteException
	{
		Entidad entidadLogica = (Entidad)entidad.getObjetoDiagramaDecorado();
		
		//verifico que las referencias externas existan en todos los diagramas donde está copiada la entidad
		this.verificarSiEntidadesEstan(entidadLogica.getReferenciaAEntidadesExternas(), entidad);

		//verifico atributos y validez de relación fuerte-débil
		List<AtributoGraficable> atributos = this.getAtributoGraficables(entidadLogica.getAtributos());
		EntidadGraficable entidadGraficable;
			
		for(GraficableConEtiqueta graficable: entidadLogica.getGraficables())
		{
			entidadGraficable = (EntidadGraficable) graficable;
			
			entidadGraficable.verificarConsistenciaDeAtributos(atributos);
			entidadGraficable.verificarSiReferenciasExternasValidas();
			//entidadGraficable.verificarIdentificadores();
		}
	}
	
	/*	 
	 * Recibe un relación graficable que fue editado y cambia todas las vistas de esa relación que podrían 
	 * encontrarse en cualquier otro diagrama
	 *  
	 * 	relacionGraficable	= la relación editada que disparó la acción. 
	 */
	public void editar(RelacionGraficable relacion, EstrategiaRelacionGraficable estrategiaRelacionGraficable) throws AtributoTipoNoSoportadoException, GraficableException, NoExisteEntidadGraficable, RelacionException
	{
		Relacion relacionLogica = (Relacion)relacion.getObjetoDiagramaDecorado();
		RelacionGraficable relacionGraficable;
		
		for(GraficableConEtiqueta graficable: relacionLogica.getGraficables())
		{
			relacionGraficable = (RelacionGraficable) graficable;
			
			relacionGraficable.setTipo(estrategiaRelacionGraficable);
			relacionGraficable.doSetAtributos(this.getAtributoGraficables(relacionLogica.getAtributos()));
			relacionGraficable.setReferenciasExternas(relacionGraficable.getDiagrama().getEntidadRolGraficables(relacionLogica.getEntidades()));
			relacionGraficable.inicializarPosiciones();
		}
	}
	
	/*	 
	 * Recibe una entidad graficable que fue editada y cambia todas las vistas de esa entidad que podrían 
	 * encontrarse en cualquier otro diagrama
	 *  
	 * 	entidadGraficable	= la entidad editada que disparó la acción. 
	 */
	public void editar(EntidadGraficable entidad, EstrategiaEntidadGraficable estrategiaEntidadGraficable) throws IdExternoInexistenteException, AtributoTipoNoSoportadoException
	{
		Entidad entidadLogica = (Entidad)entidad.getObjetoDiagramaDecorado();
		EntidadGraficable entidadGraficable;
		
		for(GraficableConEtiqueta graficable: entidadLogica.getGraficables())
		{
			entidadGraficable = (EntidadGraficable) graficable;
			
			entidadGraficable.setTipoEntidad(estrategiaEntidadGraficable);
			entidadGraficable.doSetAtributos(this.getAtributoGraficables(entidadLogica.getAtributos()));
			entidadGraficable.setReferenciasExternas(entidadGraficable.getDiagrama());
			entidadGraficable.crearIdentificadores();
			entidadGraficable.inicializarPosiciones();
		}
	}
	
	/*
	 * Tipos de datos que maneja el proyecto
	 */
	public List<FabricaEntidadGraficable> getTipoDeEntidades() {
		return ControladorDeTiposDeObjetos.getInstancia().getTipoDeEntidades();
	}

	public List<FabricaRelacionGraficable> getTipoDeRelaciones() {
		return ControladorDeTiposDeObjetos.getInstancia().getTipoDeRelaciones();
	}

	public List<FabricaAtributoGraficable> getTipoDeAtributos() {
		return ControladorDeTiposDeObjetos.getInstancia().getTipoDeAtributos();
	}
	
	public List<AtributoGraficable> getAtributoGraficables(List<Atributo> atributosLogicos) throws AtributoTipoNoSoportadoException {
		return ControladorDeTiposDeObjetos.getInstancia().getAtributoGraficables(atributosLogicos);
	}
	
	public IConversorLogicoAGraficable getConversor() {
		return ControladorDeTiposDeObjetos.getInstancia();
	}
	
	public void verificarProyecto(ContenedorDeErrores errores)
	{
		if(diagramaPpal.getDiagrama() != proyecto.getDiagramaPpal())
			errores.agregarError("Proyecto graficable: <<" + proyecto.getNombre() + ">> - No hay coincidencia entre el diagrama principal del proyecto graficable y el proyecto lógico.");
		
		diagramaPpal.validar(errores);
		
		
	}
	
	public void verificarDiagramaActual(ContenedorDeErrores errores) {
		this.diagramaActual.validarSoloDiagrama(errores);
	}
	
	public boolean existeElNombreDelDiagrama(String nombre) {
		
		if ( this.getDiagramaPpal().getDiagrama().getNombre().compareToIgnoreCase( nombre ) == 0 )
			return true;
		
		return existeElNombreEnElDiagramaHijo(nombre, this.getDiagramaPpal().getDiagrama() );
	}

	private boolean existeElNombreEnElDiagramaHijo(String nombre, Diagrama diagrama) 
	{
		for ( Diagrama diagramaHijos: diagrama.getDiagramasHijo() )
		{

			if ( diagramaHijos.getNombre().compareToIgnoreCase( nombre ) == 0 )
				return true;
			
			if ( this.existeElNombreEnElDiagramaHijo(nombre, diagramaHijos) )
				return true;
		}
		
		return false;
	}
}
